package com.jnc.core.config.shiro.session;

import com.jnc.common.constant.ShiroConstant;
import com.jnc.common.util.convert.SerializeUtil;
import com.jnc.core.config.redis.RedisStore;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.session.InvalidSessionException;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.UnknownSessionException;
import org.apache.shiro.session.mgt.SessionKey;
import org.apache.shiro.session.mgt.SimpleSession;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.apache.shiro.web.session.mgt.WebSessionKey;

import javax.servlet.ServletRequest;
import java.io.Serializable;

/**
 * @Author: jjn
 * @Date: 2018/11/1
 * @Desc:
 */
@Slf4j
public class RedisShiroSessionManager extends DefaultWebSessionManager {

    private RedisStore redisStore;

    public RedisShiroSessionManager(RedisStore redisStore) {
        this.redisStore = redisStore;
    }

    /**
     * 获取session, 优化单次请求需要多次访问redis的问题
     * @param sessionKey
     * @return
     * @throws UnknownSessionException
     */
    @Override
    protected Session retrieveSession(SessionKey sessionKey) throws UnknownSessionException {
        //获取sessionId
        Serializable sessionId = getSessionId(sessionKey);

        ServletRequest request = null;
        // 在 Web 下使用 shiro 时这个 sessionKey 是 WebSessionKey 类型的
        // 若是在web下使用，则获取request
        if(sessionKey instanceof WebSessionKey){
            request = ((WebSessionKey) sessionKey).getServletRequest();
        }

        //尝试从request中获取session
        if(request != null && sessionId != null){
            Object sessionObj = request.getAttribute(sessionId.toString());
            if(sessionObj != null){
                log.debug("Gets the session from request, sessionId: {}", sessionId);
                return (Session) sessionObj;
            }
        }

        //判断session是否已经存在,不存在，则写入Redis；避免第一次访问，总是报UnknownSessionException异常
        String sId = null;
        byte[] key = null;
        if(request != null && sessionId != null){
            sId = sessionId.toString();
            key = (ShiroConstant.SHIRO_SESSION_PREFIX + sId).getBytes();
            boolean check = redisStore.exists(key);
            if(!check){
                SimpleSession simple = new SimpleSession();
                simple.setId(sId);
                redisStore.setExpire(key, SerializeUtil.serialize(simple), ShiroConstant.EXPIRE_SECONDS);
            }
        }

        //若从request中获取session失败，则从redis中获取session，并把获取到的session存储到request中，方便下次获取
        Session session = super.retrieveSession(sessionKey);
        if(request != null && sessionId != null){
            log.debug("Store the session into the request, sessionId: {}", sessionId);
            request.setAttribute(sessionId.toString(), session);
        }
        return session;
    }

    @Override
    public void setAttribute(SessionKey sessionKey, Object attributeKey, Object value) throws InvalidSessionException {
        super.setAttribute(sessionKey, attributeKey, value);
        if(value != null && needMark(attributeKey)){
            doGetSession(sessionKey);
        }
    }

    /**
     * 判断是否需要持久化
     * @param attributeKey
     * @return
     */
    private boolean needMark(Object attributeKey){
        if (attributeKey == null){
            return false;
        }
        String attributeKeyStr = attributeKey.toString();
        // 优化 flash属性没必要持久化
        if (attributeKeyStr.startsWith("org.springframework")){
            return false;
        }
        if (attributeKeyStr.startsWith("javax.servlet")){
            return false;
        }
        if (attributeKeyStr.equals(ShiroConstant.USER_NAME)){
            return false;
        }
        return true;
    }
}
